package org.jflame.commons.util;

import org.jflame.commons.codec.Transcoder;
import org.jflame.commons.model.Chars;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.StringTokenizer;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.CharSetUtils;
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;

/**
 * 字符串工具类
 * 
 * @see org.apache.commons.lang3.StringUtils
 * @see org.apache.commons.lang3.RandomStringUtils
 * @see org.apache.commons.lang3.StringEscapeUtils
 * @author zyc
 */
public final class StringHelper extends StringUtils {

    /**
     * 查找字符串列表searchCharSequences的任意一个元素是否出现在字符串str中
     * 
     * @param str
     * @param searchCharSequences
     * @return
     * @see #containsAny(CharSequence, CharSequence...)
     */
    public static boolean containsAny(String str, Collection<String> searchCharSequences) {
        if (StringUtils.isEmpty(str) || CollectionHelper.isEmpty(searchCharSequences)) {
            return false;
        }
        for (CharSequence searchCharSequence : searchCharSequences) {
            if (StringUtils.contains(str, searchCharSequence)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 返回字符串最后一个字符
     *
     * @param str
     * @return
     */
    public static char endChar(String str) {
        return str.charAt(str.length() - 1);
    }

    /**
     * 查找指定字符中某字符串中第N次出现位置的索引
     *
     * @param text 被查找的字符串
     * @param searchChar 要搜索的字符
     * @param order 第几次出现，从1开始
     * @return 字符第N次出现位置索引
     */
    public static int indexOfAt(CharSequence text, char searchChar, int order) {
        if (order < 1) {
            throw new IllegalArgumentException("参数 'order'必须大于或等于1");
        }
        int len = text.length();
        int curOrder = 0;
        for (int i = 0; i < len; i++) {
            if (text.charAt(i) == searchChar) {
                curOrder++;
                if (curOrder == order) {
                    return i;
                }
            }
        }
        return -1;
    }

    /**
     * 将字符串插入到另一字符串的指定位置处.如果索引大于字符长度将直接加到最后.如果索引&lt;1加在最前
     *
     * @param str 被插入字符串
     * @param insertStr 要插入的字符串
     * @param index 插入的索引
     * @return
     */
    public static String insertAt(String str, String insertStr, int index) {
        if (isEmpty(str)) {
            return insertStr;
        }
        if (isEmpty(insertStr)) {
            return str;
        }
        if (index >= str.length()) {
            return str + insertStr;
        }
        if (index <= 0) {
            return insertStr + str;
        }
        return str.substring(0, index) + insertStr + str.substring(index);
    }

    /**
     * 以逗号,组合数组元素为字符串. 示例:
     *
     * <pre>
     * StringHelper.joinWithComma(["a", "b", "c"]) =&gt; "a,b,c"
     * </pre>
     *
     * @param array 数组
     * @return
     */
    public static String joinWithComma(Object[] array) {
        return StringUtils.join(array, Chars.COMMA);
    }

    /**
     * 以逗号,组合集合元素为字符串,示例:<br>
     * StringHelper.joinWithComma(["a", "b", "c"]) = "a,b,c"
     *
     * @param iterable 数组
     * @return
     */
    public static String joinWithComma(Iterable<?> iterable) {
        return StringUtils.join(iterable, Chars.COMMA);
    }

    /**
     * 以指定分隔符,前缀,后缀组合集合元素为字符串,示例:
     * 
     * <pre>
     * 
     * List<String> list = Arrays.asList("a", "b", "c");
     * String str = StringHelper.joinWith(list, ",", "{", "}");// 结果: "{a,b,c}"
     * </pre>
     * 
     * @param collection 集合
     * @param separator 分隔符
     * @param prefix 前缀
     * @param suffix 后缀
     * @return
     */
    public static String joinWith(final Collection<?> collection, final CharSequence separator,
            final CharSequence prefix, final CharSequence suffix) {
        if (collection == null) {
            return null;
        }
        return joinWith(collection.iterator(), separator, prefix, suffix);
    }

    /**
     * 以指定分隔符,前缀,后缀组合集合元素为字符串,示例:
     *
     * <pre>
     * 
     * List<String> list = Arrays.asList("a", "b", "c");
     * String str = StringHelper.joinWith(list.iterator(), ",", "{", "}");// 结果: "{a,b,c}"
     * </pre>
     * 
     * @param iterator 迭代器
     * @param separator 分隔符
     * @param prefix 前缀
     * @param suffix 后缀
     * @return
     */
    public static String joinWith(final Iterator<?> iterator, final CharSequence separator, final CharSequence prefix,
            final CharSequence suffix) {
        if (iterator == null) {
            return null;
        }
        /* if (!iterator.hasNext()) {
            return EMPTY;
        }*/
        StringJoiner joiner = new StringJoiner(separator, prefix, suffix);
        while (iterator.hasNext()) {
            joiner.add(Objects.toString(iterator.next(), EMPTY));
        }

        return joiner.toString();
    }

    public static String joinWithComma(final Collection<?> collection, final CharSequence prefix,
            final CharSequence suffix) {
        return StringHelper.joinWith(collection, ",", prefix, suffix);
    }

    /**
     * 使用用英文逗号","拆分字符串为数组
     * 
     * @param str 待拆分字符串
     * @return 拆分后字符串数组,如果传null返回null
     */
    public static String[] split(String str) {
        return StringUtils.split(str, Chars.COMMA);
    }

    /**
     * 拆分字符串并转列表
     * 
     * @param str 待拆分字符串
     * @param separatorChar 分隔符
     * @return
     */
    public static List<String> splitAsList(String str, String separatorChar) {
        String[] tokens = StringUtils.split(str, separatorChar);
        return tokens == null ? null : CollectionHelper.newList(tokens);
    }

    /**
     * 拆分字符串并转Integer列表
     * 
     * @param str 待拆分字符串
     * @param separatorChar 分隔符
     * @return
     * @throws NumberFormatException
     */
    public static List<Integer> splitAsIntList(String str, String separatorChar) {
        if (StringHelper.isEmpty(str)) {
            return Collections.emptyList();
        }
        String[] tmpArr = StringUtils.split(str, separatorChar);
        List<Integer> lst = new ArrayList<>(tmpArr.length);
        for (String tmp : tmpArr) {
            lst.add(Integer.valueOf(tmp.trim()));
        }
        return lst;
    }

    /**
     * 拆分字符串,返回字符串列表
     * 
     * @param str
     * @param delimiters 分隔符
     * @param trimTokens 是否对拆分后元素执行trim操作
     * @param ignoreEmptyTokens 是否忽略空串
     * @return 字符串List
     */
    public static List<String> tokenizeToStrings(String str, String delimiters, boolean trimTokens,
            boolean ignoreEmptyTokens) {
        if (str == null) {
            return null;
        }
        StringTokenizer st = new StringTokenizer(str, delimiters);
        List<String> tokens = new ArrayList<String>();
        while (st.hasMoreTokens()) {
            String token = st.nextToken();
            if (trimTokens) {
                token = token.trim();
            }
            if (!ignoreEmptyTokens || token.length() > 0) {
                tokens.add(token);
            }
        }
        return tokens;
    }

    /**
     * 拆分字符串,返回数组
     * 
     * @param str
     * @param delimiters 分隔符
     * @param trimTokens 是否对拆分后元素执行trim操作
     * @param ignoreEmptyTokens 是否忽略空串
     * @return String[]
     */
    public static String[] tokenizeToStringArray(String str, String delimiters, boolean trimTokens,
            boolean ignoreEmptyTokens) {
        List<String> tokens = tokenizeToStrings(str, delimiters, trimTokens, ignoreEmptyTokens);
        if (tokens == null) {
            return null;
        }
        return ArrayHelper.toArray(tokens);
    }

    /**
     * 将url参数字符串转为Map 如:x=1&amp;y=2转为 map.put("x","1").
     * 
     * @see #buildUrlParamFromMap(Map)
     * @param paramStr url参数字符串, 如:x=1&amp;y=2
     * @return Map&lt;String, String&gt;
     */
    public static Map<String,String> buildMapFromUrlParam(String paramStr) {
        if (isEmpty(paramStr)) {
            return null;
        }
        Map<String,String> map = new HashMap<String,String>();
        String[] tmpArr = split(paramStr, Chars.AND);
        String[] kv;
        if (tmpArr != null) {
            for (String tmp : tmpArr) {
                kv = split(tmp, Chars.EQUAL);
                map.put(kv[0].trim(), kv.length >= 2 ? Transcoder.urlDecode(kv[1]) : StringUtils.EMPTY);
            }
        }
        return map;
    }

    /**
     * 将map转为url参数字符串 如:key=value&amp;key1=value1,参数值做url编码
     * 
     * @param paramMap Map&lt;String, String&gt;
     * @see #buildMapFromUrlParam(String)
     * @return url参数字符串, 如:x=1&amp;y=2
     */
    public static String buildUrlParamFromMap(Map<String,?> paramMap) {
        return buildUrlParamFromMap(paramMap, true);
    }

    /**
     * 将map转为url参数字符串 如:key=value&amp;key1=value1
     * 
     * @param paramMap
     * @param isUrlEncode 是否url编码
     * @return
     */
    public static String buildUrlParamFromMap(Map<String,?> paramMap, boolean isUrlEncode) {
        if (paramMap == null || paramMap.isEmpty()) {
            return null;
        }
        StringBuilder strBuf = new StringBuilder(paramMap.size() * 10);
        for (Entry<String,?> kv : paramMap.entrySet()) {
            if (Objects.isNull(kv.getValue()) || "".equals(kv.getValue())) {
                continue;
            }
            if (kv.getValue() != null) {
                strBuf.append(Chars.AND)
                        .append(kv.getKey())
                        .append(Chars.EQUAL)
                        .append(isUrlEncode ? Transcoder.urlEncode(kv.getValue()
                                .toString()) : kv.getValue());
            }
        }
        strBuf.deleteCharAt(0);
        return strBuf.toString();
    }

    /**
     * 将驼峰命名的字符串转为下划线分隔形式
     * 
     * @param camelStr 驼峰命名的字符串
     * @return
     */
    public static String camelToUnderline(String camelStr) {
        return camelCaseToSpecSymbolCase(camelStr, '_');
    }

    /**
     * 将驼峰命名的字符串转为中划线分隔形式
     * 
     * @param camelStr
     * @return
     */
    public static String camelToKebabCase(String camelStr) {
        return camelCaseToSpecSymbolCase(camelStr, '-');
    }

    private static String camelCaseToSpecSymbolCase(String camelStr, final char specSymbol) {
        int len = camelStr.length();
        StringBuilder sb = new StringBuilder(len);
        for (int i = 0; i < len; i++) {
            char c = camelStr.charAt(i);
            if (Character.isUpperCase(c)) {
                if (i > 0) {
                    sb.append(specSymbol);
                }
                sb.append(Character.toLowerCase(c));
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    /**
     * 下划线分隔形式转为驼峰命名字符串
     * 
     * @param underlineStr 下划线分隔形式的字符串
     * @return
     */
    public static String underlineToCamel(String underlineStr) {
        return underlineToCamel(underlineStr, false);
    }

    /**
     * 下划线分隔形式转为驼峰命名字符串
     * 
     * @param str
     * @param isUpperFirstChar 首字母是否大写
     * @return
     */
    public static String underlineToCamel(String str, boolean isUpperFirstChar) {
        return specSymbolCaseToCamelCase(str, '_', isUpperFirstChar);
    }

    private static String specSymbolCaseToCamelCase(String str, final char specSymbol, final boolean isUpperFirstChar) {
        int len = str.length();
        StringBuilder sb = new StringBuilder(len);
        for (int i = 0; i < len; i++) {
            char c = str.charAt(i);
            if (c == specSymbol) {
                if (++i < len) {
                    sb.append(Character.toUpperCase(str.charAt(i)));
                }
            } else {
                if (i == 0 && isUpperFirstChar) {
                    sb.append(Character.toTitleCase(c));
                } else {
                    sb.append(c);
                }
            }
        }
        return sb.toString();
    }

    /**
     * 替换html文本中的特殊符号为html实体
     * 
     * @param htmlText html文本
     * @see #unescapeHTML(String)
     * @see org.apache.commons.lang3.StringEscapeUtils
     * @return
     */
    public static String escapeHTML(String htmlText) {
        return StringEscapeUtils.escapeHtml4(htmlText);
    }

    /**
     * 将文本中的html实体符还原回html符号
     * 
     * @param escapeHtmlText html文本
     * @see #escapeHTML(String)
     * @see org.apache.commons.lang3.StringEscapeUtils
     * @return
     */
    public static String unescapeHTML(String escapeHtmlText) {
        return StringEscapeUtils.unescapeHtml4(escapeHtmlText);
    }

    // script正则{或<script[^>]*?>[\\s\\S]*?<\\/script>
    public static String HTML_SCRIPT_REGEX = "<[\\s]*?script[^>]*?>[\\s\\S]*?<[\\s]*?\\/[\\s]*?script[\\s]*?>";
    // style正则{或<style[^>]*?>[\\s\\S]*?<\\/style>
    public static String HTML_STYLE_REGEX = "<[\\s]*?style[^>]*?>[\\s\\S]*?<[\\s]*?\\/[\\s]*?style[\\s]*?>";
    // HTML标签正则
    public static String HTML_TAG_REGEX = "<[^>]+>";

    /**
     * 删除文本中的html标签
     * 
     * @param htmlText html文本
     * @return
     */
    public static String removeHtmlTag(String htmlText) {
        if (StringUtils.isEmpty(htmlText)) {
            return htmlText;
        }
        // 过滤script标签
        String htmlStr = RegExUtils.removePattern(htmlText, HTML_SCRIPT_REGEX);
        htmlStr = RegExUtils.removePattern(htmlStr, HTML_STYLE_REGEX);
        return RegExUtils.removePattern(htmlStr, HTML_TAG_REGEX);
    }

    /**
     * 返回一个字符串中指定分隔串之后的字符串，忽略分隔串的大小写.
     * <p>
     * 补充commons-lang 的StringUtils.substringAfter方法
     * 
     * @param str 指定字符串
     * @param separator 分隔字符串
     * @return
     */
    public static String substringAfterIgnoreCase(String str, String separator) {
        if (isEmpty(str)) {
            return str;
        }
        if (separator == null) {
            return StringUtils.EMPTY;
        }
        int pos = StringUtils.indexOfIgnoreCase(str, separator);
        if (pos == StringUtils.INDEX_NOT_FOUND) {
            return StringUtils.EMPTY;
        }
        return str.substring(pos + separator.length());
    }

    /**
     * 返回一个字符串中指定分隔串之前的字符串，忽略分隔串的大小写。.
     * <p>
     * 补充commons-lang 的StringUtils.substringBefore方法
     * 
     * @param str 指定字符串
     * @param separator 分隔字符串
     * @return
     */
    public static String substringBeforeIgnoreCase(String str, String separator) {
        if (isEmpty(str) || separator == null) {
            return str;
        }
        if (separator.length() == 0) {
            return StringUtils.EMPTY;
        }
        int pos = StringUtils.indexOfIgnoreCase(str, separator);
        if (pos == StringUtils.INDEX_NOT_FOUND) {
            return str;
        }
        return str.substring(0, pos);
    }

    /**
     * 判断字符串是否以给定的多个字符串中任意一个开始
     * 
     * @param str
     * @param searchStrings
     * @return
     */
    public static boolean startsWithIgnoreCase(final CharSequence str, final CharSequence... searchStrings) {
        if (StringUtils.isEmpty(str) || ArrayUtils.isEmpty(searchStrings)) {
            return false;
        }
        for (final CharSequence searchString : searchStrings) {
            if (StringUtils.startsWithIgnoreCase(str, searchString)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 打乱字符串顺序
     *
     * @param str
     * @return 返回一个新的字符串
     */
    public static String shuffle(String str) {
        char[] arr = str.toCharArray();
        ArrayHelper.shuffle(arr);
        return new String(arr);
    }

    /**
     * 从字符串中删除字符，指定起始位和长度
     * 
     * @param str 待删除字符串
     * @param startIndex 指定索引下标，包含在删除内
     * @param length 指定要删除的长度
     * @return
     */
    public static String remove(final String str, int startIndex, int length) {
        if (isNotEmpty(str)) {
            if (startIndex < 0) {
                startIndex = 0;
            }
            char[] strToCharArr = str.toCharArray();
            if (startIndex + length >= strToCharArr.length) {
                length = strToCharArr.length - startIndex;
            }
            int[] removedSub = new int[length];
            int i = 0;
            while (i < length) {
                removedSub[i] = startIndex + i;
                i++;
            }
            char[] newCharArr = ArrayUtils.removeAll(strToCharArr, removedSub);
            return new String(newCharArr);

        }
        return str;
    }

    /**
     * 删除最后一个字符
     * 
     * @param str String
     * @return 返回新的字符串
     */
    public static String removeLast(final String str) {
        return StringUtils.substring(str, 0, str.length() - 1);
    }

    /**
     * 删除字符串str中在set出现过的任意字符
     * 
     * <pre>
     * StringHelper.remove("hello", "hl") = "eo"
     * StringHelper.remove("hello", "le")  = "ho"
     * StringHelper.remove(null, *) = null
     * </pre>
     * 
     * @param str String 待删除字符串
     * @param set String[] 要查找的字符串集合
     * @return
     */
    public static String removeAny(String str, String... set) {
        return CharSetUtils.delete(str, set);
    }

    /**
     * 判断字符串是否为空或null,如果是抛出IllegalArgumentException异常
     * 
     * @param str
     * @param message 异常错误信息
     */
    public static void requireNonEmpty(CharSequence str, String message) {
        if (StringUtils.isEmpty(str)) {
            throw new IllegalArgumentException(message);
        }
    }

    /**
     * 字符串覆盖处理,用*替换头尾保留字符个数之外的中间部分
     * 
     * @param str 待处理字符串
     * @param head 头部保留字符个数
     * @param tail 尾部保留字符个数
     * @return
     * @see #masked(String, int, int, char, int)
     */
    public static String masked(final String str, int head, int tail) {
        return masked(str, head, tail, '*', 0);
    }

    /**
     * 字符串覆盖处理,用指定字符替换头尾保留字符个数之外的中间部分. <br>
     * 如传入null或空字符原样返回,示例:
     * 
     * <pre>
     * StringHelper.masked(null, 1, 1, '*',0)       = null
     * StringHelper.masked("", 1, 1, '*',0)         = ""
     * StringHelper.masked("abcdef", 1, 2, '*',0)   = "a***ef"
     * StringHelper.masked("abcdef", 1, 2, '*',2)   = "a**ef"
     * StringHelper.masked("abcdef", 0, 2, '*',0)   = "****ef"
     * StringHelper.masked("abcdef", 6, 1, '*',0)   = "******"
     * StringHelper.masked("abcdef", -1, 2, '*',2)  = "a**ef"
     * </pre>
     * 
     * @param str 待处理字符串
     * @param head 头部保留字符个数
     * @param tail 尾部保留字符个数
     * @param symbol 替换符
     * @param symbolSize 替换符个数,如果小于等于0表示不固定个数,即替换不需保留的所有字符
     * @return
     * @see #overlay(String, String, int, int)
     */
    public static String masked(final String str, int head, int tail, final char symbol, int symbolSize) {
        if (str == null) {
            return null;
        }
        if (str.isEmpty()) {
            return str;
        }
        final int len = str.length();
        if (head < 0) {
            head = 0;
        }
        if (tail < 0) {
            tail = 0;
        }
        if (head == 0 && tail == 0 && symbolSize <= 0) {
            return str;
        }
        // 头部或尾部保留长度大于整体字符串长度不替换
        if (head > len || tail > len) {
            return str;
        }

        String overlayPart;
        if (symbolSize <= 0) {
            int overlaySize = len - head - tail;
            if (overlaySize <= 0) {
                return StringUtils.repeat(symbol, len);// 替换了所有
            } else {
                overlayPart = StringUtils.repeat(symbol, overlaySize);// 替换符与剩余部分长度相等
            }
        } else {
            overlayPart = StringUtils.repeat(symbol, symbolSize);// 固定个数
        }

        return str.substring(0, head) + overlayPart + str.substring(len - tail);

    }

}
